﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace Chat
{
    public class Client
    {
        protected string _host = "localhost";
        protected int _port = 4520; /* Needs to be 4502 - 4532 for silverlight */

        protected string _name = "Chat User";

        protected bool _isSignedIn = false;

        protected UserSocket _userSocket;

        public event EventHandler<MessageEventArgs> UserJoined = delegate { };
        public event EventHandler<MessageEventArgs> UserLeft = delegate { };
        public event EventHandler<MessageEventArgs> ChatReceived = delegate { };
        public event EventHandler<MessageEventArgs> UserList = delegate { };
        
        public event EventHandler<EventArgs> SignedIn = delegate { };
        public event EventHandler<EventArgs> SignedOut = delegate { };

        public event EventHandler<ClientEventArgs> SystemEvent = delegate { };
        public event EventHandler<ClientEventArgs> SignInFailed = delegate { };

        public bool IsSignedIn 
        {
            get { return _isSignedIn; }
            set { _isSignedIn = value; }
        }

        public string Name
        {
            get { return _name; }
        }

        public Client(string host, int port, string name)
        {
            _host = host;
            _port = port;
            _name = name;
        }
        
        public void LogSystemEvent(string eventText)
        {
            SystemEvent(this, new ClientEventArgs(DateTime.Now, eventText));
        }

        public void SignIn()
        {
            if (!_isSignedIn)
            {
                this.connect();
            }
        }

        public void SignOut()
        {
            if (_isSignedIn)
            {
                this.disconnect();
            }

            SignedOut(this, EventArgs.Empty);
        }

        public void SendChat(string text)
        {
            if (!_isSignedIn)
                throw new InvalidOperationException("Not connected to server, SendChat Failed");

            Message chatMessage = new Message(DateTime.Now, MessageType.Text, new string[] { this.Name, text });
            _userSocket.Send(chatMessage);
        }

        protected void disconnect()
        {
            Message disconnectMessage = new Message(DateTime.Now, MessageType.Disconnect, new string[] { });
            _userSocket.Send(disconnectMessage);

            _isSignedIn = false;
            _userSocket.Close();
        }

        protected void connect()
        {
            LogSystemEvent("Attempting to connect to " + _host + " on port " + _port.ToString());

            EndPoint endPoint = null;

#if SILVERLIGHT
            endPoint = new DnsEndPoint(_host, _port);
#else
            IPHostEntry hostEntry = Dns.GetHostEntry(_host);

            IPAddress address = (from a in hostEntry.AddressList where a.AddressFamily == AddressFamily.InterNetwork select a).First();
            endPoint = new IPEndPoint(address, _port);
#endif
            
            Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.UserToken = connectSocket;
            args.RemoteEndPoint = endPoint;
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
            if (!connectSocket.ConnectAsync(args))
                OnSocketConnectCompleted(this, args);
        }

        protected void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                SystemEvent(this, new ClientEventArgs(DateTime.Now, "Connection Successful"));

                // Start receiving data
                _isSignedIn = true;

                Socket connectSocket = e.UserToken as Socket;
                _userSocket = new UserSocket(connectSocket, ReceiveMode.Automatic);
                _userSocket.NewMessage += new EventHandler<MessageEventArgs>(_userSocket_NewMessage);
               
                // Need to complete the sign in with setting our name
                Message setNameRequest = new Message(DateTime.Now, MessageType.SetNameRequest, new string[] { _name });
                _userSocket.Send(setNameRequest);
            }
            else
            {
                LogSystemEvent("Error Occurred Connecting to the socket: " + e.SocketError.ToString());

                if (!_isSignedIn)
                    SignInFailed(this, new ClientEventArgs(DateTime.Now, "Couldn't connect to server"));
            }
        }

        private void _userSocket_NewMessage(object sender, MessageEventArgs e)
        {
            Message message = e.Message;

            if (message.Type == MessageType.Text)
                ChatReceived(this, new MessageEventArgs(message));
            else if (message.Type == MessageType.UserList)
                UserList(this, new MessageEventArgs(message));
            else if (message.Type == MessageType.UserJoined)
                UserJoined(this, new MessageEventArgs(message));
            else if (message.Type == MessageType.UserLeft)
            {
                LogSystemEvent("User Left Received");
                UserLeft(this, new MessageEventArgs(message));
            }
            else if (message.Type == MessageType.NameSet)
            {
                SignedIn(this, EventArgs.Empty);
                Message userListRequest = new Message(DateTime.Now, MessageType.UserListRequest, new string[] { });
                _userSocket.Send(userListRequest);
            }
            else if (message.Type == MessageType.SetNameDenied)
            {
                SignInFailed(this, new ClientEventArgs(DateTime.Now, message.Data[0]));
            }
        }

        private void _userSocket_Error(object sender, ClientEventArgs e)
        {
            LogSystemEvent("Error Occurred: " + e.Text);
        }
    }
}